﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Kinect;
using System.Windows.Media.Imaging;
using System.Windows.Media;
using System.IO;
using System.Windows;
using System.Drawing;

namespace KinectDrawer
{

    public enum DrawingState
    {
        UserIsDrawing,
        UnsufficentDetection,
        NoDetection
    }

    public class Drawer
    {
        public event EventHandler<DrawerRealtimeDrawingEventArgs> RealtimeDrawingReady;
        public event EventHandler<DrawerUserDrawingReadyEventArgs> UserDrawingReady;

        //Capteur de la kinect
        public KinectSensor sensor;

        //Bitmap qui contiendra les infos sur la couleur de l'image
        private WriteableBitmap colorBitmap;

        //Stockage intermediaire pour les pixels couleur reçus par la camera
        private byte[] colorPixels;

        //Points de profondeur
        private DepthImagePixel[] depthPixels;


        public void StartSensors()
        {
            //Regarde les capteurs et charge le premier trouvé
            foreach (var potentialSensor in KinectSensor.KinectSensors)
            {
                if (potentialSensor.Status == KinectStatus.Connected)
                {
                    this.sensor = potentialSensor;
                    break;
                }
            }

            //Si le capteur identifié n'est pas null
            if (null != this.sensor)
            {
                // Active le flux de profondeur avec une résolution définie
                this.sensor.DepthStream.Enable(DepthImageFormat.Resolution640x480Fps30);

                // Allocation d'espace pour les pixels de profondeur
                this.depthPixels = new DepthImagePixel[this.sensor.DepthStream.FramePixelDataLength];

                // Active le flux de couleur
                this.sensor.ColorStream.Enable(ColorImageFormat.RgbResolution640x480Fps30);

                // Allocation d'espace pour les pixels de couleur
                this.colorPixels = new byte[this.sensor.ColorStream.FramePixelDataLength];

                // L'image à afficher
                this.colorBitmap = new WriteableBitmap(this.sensor.ColorStream.FrameWidth, this.sensor.ColorStream.FrameHeight, 96.0, 96.0, PixelFormats.Bgr32, null);

                // Ajoute un observateur appelé à chaque fois qu'il y a des nouvelles données de profondeur
                this.sensor.DepthFrameReady += this.SensorDepthFrameReady;

                // Ajoute un observateur appelé à chaque fois qu'il y a des nouvelles données de couleur
                this.sensor.ColorFrameReady += this.SensorColorFrameReady;

                // Démarre le capteur
                try
                {
                    this.sensor.Start();
                }
                catch (IOException)
                {
                    this.sensor = null;
                }


            }

            if (null == this.sensor)
            {
                Console.WriteLine("A Pas Kinect");
                //todo: message pas de kinect !!
            }
            else
            {
                userDrawing = new UserDrawing(this.sensor.DepthStream.FrameWidth, this.sensor.DepthStream.FrameHeight);
            }


        }

        //Une image de couleur est prête
        private void SensorColorFrameReady(object sender, ColorImageFrameReadyEventArgs e)
        {
            using (ColorImageFrame colorFrame = e.OpenColorImageFrame())
            {
                if (colorFrame != null)
                {
                    // Copy the pixel data from the image to a temporary array
                    colorFrame.CopyPixelDataTo(this.colorPixels);

                    // Write the pixel data into our bitmap
                    this.colorBitmap.WritePixels(
                        new Int32Rect(0, 0, this.colorBitmap.PixelWidth, this.colorBitmap.PixelHeight),
                        this.colorPixels,
                        this.colorBitmap.PixelWidth * sizeof(int),
                        0);
                }
            }
        }

        //Une liste de listes de points, chacune de ces dernières étant donc une ligne
        private UserDrawing userDrawing;

        private long workFramesCounter = 0;
        private int WORK_RATE = 3;



        //Nombre max d'images sans détection avant d'arrêter la ligne en cours
        private int MAXIMUM_FRAMES_WITHOUT_INTERACTION_TO_COMPLETE_LINE = 4;
        //D'arrêter le dessin en cours
        private int MAXIMUM_FRAMES_WITHOUT_INTERACTION_TO_COMPLETE_DRAWING = 30;

        //Compteur utilisé pour définir le nombres d'images sans détection depuis la dernière détection
        //Pour que cette valeur ne soit pas en deça d'un seuil au démarrage, on l'initialise au seuil max +1
        private int workFramesWithoutInteraction = 14;

        //Booléen indiquant si l'utilisateur est en train de dessiner
        private DrawingState drawingState = DrawingState.NoDetection;

        //Une image de profondeur est prête
        private void SensorDepthFrameReady(object sender, DepthImageFrameReadyEventArgs e)
        {
            using (DepthImageFrame depthFrame = e.OpenDepthImageFrame())
            {
                if (depthFrame != null)
                {
                    workFramesCounter++;
                    if (workFramesCounter % WORK_RATE == 0)
                    {
                        // Copie des données de profondeur des pixels dans une variable temporaire
                        depthFrame.CopyDepthImagePixelDataTo(this.depthPixels);

                        //On cherche un pixel dans la zone de detection
                        System.Drawing.Point foundPixel = FindPixelInZone(depthPixels);

                        //Si on en a trouvé un
                        if (foundPixel.X != -1)
                        {
                            //Et que l'utilisateur dessinait déjà à la frame precédente
                            if (drawingState.Equals(DrawingState.UserIsDrawing))
                            {
                                //On ajoute le point au dessin
                                userDrawing.AddPoint(foundPixel, false);
                                workFramesWithoutInteraction = 0;
                            }
                            else
                            {
                                //S'il ne dessinait pas, mais qu'il dessinait il y a peu (perte rapide de detection)
                                if (workFramesWithoutInteraction < MAXIMUM_FRAMES_WITHOUT_INTERACTION_TO_COMPLETE_LINE)
                                {
                                    //On ajoute le point
                                    userDrawing.AddPoint(foundPixel, false);
                                    drawingState = DrawingState.UserIsDrawing;
                                    workFramesWithoutInteraction = 0;
                                }
                                else
                                {
                                    //Sinon, on calcul le pourcentage de point se trouvant dans la zone de détection autour du premier point trouvé
                                    //Si ce pourcentage est suffisant
                                    if (ZoneDoesCoverMinimumPercentageForDetection(depthPixels, foundPixel))
                                    {
                                        //On ajoute le point dans une nouvelle ligne
                                        userDrawing.AddPoint(foundPixel, true);
                                        drawingState = DrawingState.UserIsDrawing;
                                        workFramesWithoutInteraction = 0;
                                    }
                                    else
                                    {
                                        //Sinon, on signale la détection insuffisante
                                        drawingState = DrawingState.UnsufficentDetection;
                                        workFramesWithoutInteraction++;
                                    }
                                }
                            }
                        }
                        else
                        {
                            //Si aucun point n'est détecté dans la zone, on signale qu'il n'y a pas de détection
                            drawingState = DrawingState.NoDetection;
                            workFramesWithoutInteraction++;
                        }

                        //On génère l'image
                        BitmapSource userImageDrawing = userDrawing.ToImage();

                        //Si l'utilisateur semble avoir fini son dessin (qu'il ne dessine pas depuis un certain temps, et que le dessin n'est pas vide)
                        if (workFramesWithoutInteraction > MAXIMUM_FRAMES_WITHOUT_INTERACTION_TO_COMPLETE_DRAWING && (!userDrawing.IsEmpty))
                        {
                            //On signale la fin du dessin

                            //event drawing ready
                            if (UserDrawingReady != null)
                            {
                                UserDrawingReady(this, new DrawerUserDrawingReadyEventArgs(userImageDrawing, userDrawing.GetRawData(), this.sensor.DepthStream.FrameWidth, this.sensor.DepthStream.FrameHeight));
                            }
                            userDrawing.Clear();
                        }

                        //Dans tous les cas, on signale la nouvelle frame
                        if (RealtimeDrawingReady != null)
                        {
                            //Creating point as a ratio
                            Double _dFoundPixelX;
                            Double _dFoundPixelY;
                            Double _dDepthFrameWidth;
                            Double _dDepthFrameHeight;
                            Double _dResultX;
                            Double _dResultY;

                            _dFoundPixelX = (Double)foundPixel.X;
                            _dFoundPixelY = (Double)foundPixel.Y;

                            _dDepthFrameWidth = (Double) this.sensor.DepthStream.FrameWidth;
                            _dDepthFrameHeight = (Double) this.sensor.DepthStream.FrameHeight;

                            _dResultX = _dFoundPixelX / _dDepthFrameWidth;
                            _dResultY = _dFoundPixelY / _dDepthFrameHeight;
                            System.Windows.Point winPoint = new System.Windows.Point(_dResultX, _dResultY);

                            RealtimeDrawingReady(this, new DrawerRealtimeDrawingEventArgs(userDrawing.PrintDrawingOnBitmap(colorBitmap), drawingState, winPoint));
                        }
                    }
                }
            }
        }


        //Seuils de détection
        private int MINIMUM_DISTANCE_THRESHOLD = 0;
        private int MAXIMUM_DISTANCE_THRESHOLD = 1400;

        private bool PixelIsInDetectionZone(int pixelDepth)
        {
            return (pixelDepth > MINIMUM_DISTANCE_THRESHOLD && pixelDepth < MAXIMUM_DISTANCE_THRESHOLD);
        }



        //Pourcentage minimum de points trouvés se situant entre les seuils et dans la zone de détection pour commencer le dessin
        private float MINIMUM_PERCENTAGE_IN_ZONE_FOR_DETECTION = 50.0F;

        private bool ZoneDoesCoverMinimumPercentageForDetection(DepthImagePixel[] depthPixels, System.Drawing.Point pixel)
        {
            float zonePercentage = DetectionPercentageForZone(depthPixels, pixel);
            return (zonePercentage >= MINIMUM_PERCENTAGE_IN_ZONE_FOR_DETECTION);
        }

        private int DEPTH_PIXELS_SKIP_LENGTH = 3;

        private System.Drawing.Point FindPixelInZone(DepthImagePixel[] depthPixels)
        {
            //Itérateur sur les pixels de l'image courante
            int i = 0;

            //Booléen indiquant si des pixels ont été trouvés entre les seuils
            bool found = false;
            int pixelDepth;

            //Pour chaque pixel, jusqu'à la fin de l'image, on compare les profondeurs et on prend le point le plus proche
            int nearestPixelPosition = 0;
            int nearestPixelDepth = Int32.MaxValue;
            while (i < depthPixels.Length)
            {
                pixelDepth = depthPixels[i].Depth;

                //Si la profondeur de ce pixel est comprise entre les seuils
                if (PixelIsInDetectionZone(pixelDepth))
                {
                    //Un pixel a été trouvé
                    if (pixelDepth < nearestPixelDepth)
                    {
                        nearestPixelDepth = pixelDepth;
                        nearestPixelPosition = i;
                    }
                }
                i += DEPTH_PIXELS_SKIP_LENGTH;
            }

            if (nearestPixelDepth != Int32.MaxValue) //Si on a trouvé un pixel dans la zone, on en calcul les coordonées et on le retourne
            {
                //On calcule les coordonnées depuis la largeur de l'image
                int x = nearestPixelPosition % this.sensor.DepthStream.FrameWidth;
                int y = (nearestPixelPosition - x) / this.sensor.DepthStream.FrameWidth;

                return new System.Drawing.Point(x, y);
            }//Sinon on signal qu'aucun pixel n'a été trouvé
            else return new System.Drawing.Point(-1, -1);

        }


        //Taille de la zone à analyser autour du point detecté
        private int ANALYZE_ZONE_SIZE = 25;

        private float DetectionPercentageForZone(DepthImagePixel[] depthPixels, System.Drawing.Point point)
        {
            int indexInRaster = point.X + (point.Y * this.sensor.DepthStream.FrameWidth);

            int nbOfPixelsInDistanceRange = 0;
            int squareZoneSize = (ANALYZE_ZONE_SIZE + 1) * 2 * (ANALYZE_ZONE_SIZE + 1) * 2;
            int range = ANALYZE_ZONE_SIZE * 2 + 1;

            int start;
            int curPix;
            for (int line = 0; line < range; line++)
            {
                start = line * this.sensor.DepthStream.FrameWidth + indexInRaster;
                for (int col = 0; col < range; col++)
                {
                    curPix = col + start - ANALYZE_ZONE_SIZE;
                    if (curPix > 0 && curPix < this.depthPixels.Length)
                    {
                        int depth = this.depthPixels[curPix].Depth;
                        if (PixelIsInDetectionZone(depth))
                        {
                            nbOfPixelsInDistanceRange++;
                            this.depthPixels[curPix].Depth = 0;
                        }
                        else { this.depthPixels[curPix].Depth = 255; }
                    }

                }
            }

            return ((float)nbOfPixelsInDistanceRange / (float)squareZoneSize) * 100;
        }

    }


}
